﻿using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using Dokan;
using MediaAssistant.DAL;
using MediaAssistant.DAL.Helper;
using MefBasic.Data;
using MefBasic.Threading;

namespace MediaFS
{
    public class MediaFSManager : ANotifyPropertyChanged
    {
        private static readonly object LockObject = new object();
        private volatile static MediaFSManager _instance;
        public static MediaFSManager Instance
        {
            get
            {
                if (_instance == null)
                {
                    lock (LockObject)
                    {
                        if (_instance == null)
                        {
                            _instance = new MediaFSManager();
                        }
                    }
                }
                return _instance;
            }
        }

        public Profile SelectedProfile { get; set; }

        private MediaFSManager()
        {
        }

        public void Mount()
        {
            IsMounted = true;
            var mountPoint = MountMediaFS();
            Thread.Sleep(1000);
            if (DriveInfo.GetDrives().Any(d => d.Name.Equals(mountPoint, StringComparison.CurrentCultureIgnoreCase)))
                SystemHelper.Start(mountPoint);
        }

        private string MountMediaFS()
        {
            var mountPoint = string.Format("{0}:\\", DriveLatter);
            var job = new Job(j => StartDokanMain(mountPoint, j));
            job.JobCompleted += HandleJobCompleted;
            job.Start();
            return mountPoint;
        }

        public void InitializeMemoryOperations()
        {
            MediaFSOperations = new MemoryFSOperations();
            var job = new Job(j => MediaFSOperations.Initialize(WaitScreenService));
            job.StartBlocked();
        }

        private void StartDokanMain(string mountPoint, Job j)
        {
            try
            {
                DriveLatter = GetDriveLetter();
                var opt = new DokanOptions
                              {
                                  DebugMode = true,
                                  MountPoint = mountPoint,
                                  ThreadCount = 5,
                                  VolumeLabel = "Media"
                              };
                var status = DokanNet.DokanMain(opt, MediaFSOperations);
                j.Store.Add(status, "Status");
            }
            catch (Exception ex)
            {
                IsMounted = false;
                j.Store.Add(ex);
            }
        }

        private void HandleJobCompleted(object sender, EventArgs e)
        {
            var job = (Job)sender;
            var exception = job.Store.GetObject<Exception>();

            if (exception != null && exception.Message.StartsWith("Unable to load DLL 'dokan.dll': The specified module could not be found."))
            {
                if (MessageBox.Show(@"Media drive uses Dokan filesystem driver which is not installed. Do you want to install it?", @"Dokan Library Not Installed", MessageBoxButtons.YesNo) == DialogResult.Yes)
                {
                    var tempPath = Path.GetTempPath();
                    var installerPath = Path.Combine(tempPath, "DokanInstall_0.6.0.exe");
                    if (File.Exists(installerPath) == false)
                    {
                        ResourceHelper.CopyEmbededResource("MediaFS.DokanInstall_0.6.0.exe", installerPath);
                    }
                    SystemHelper.StartAndWait(installerPath);
                    Mount();
                }
            }
            var status = job.Store.GetObject<int>("Status");
            switch (status)
            {
                case DokanNet.DOKAN_DRIVER_INSTALL_ERROR:
                    MessageBox.Show(@"Driver install error", @"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    break;
                case DokanNet.DOKAN_START_ERROR:
                    MessageBox.Show(@"Start error", @"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    break;
                case DokanNet.DOKAN_ERROR:
                    MessageBox.Show(@"Unknown error", @"Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
                    break;
                case DokanNet.DOKAN_SUCCESS:
                    break;
                default:
                    Unmount();
                    Mount();
                    break;
            }
        }

        private static char GetDriveLetter()
        {
            var driveChar = 'D';
            for (var i = 0; i < 26; i++)
            {
                var driveStr = string.Format("{0}:\\", driveChar);
                if (DriveInfo.GetDrives().All(d => d.Name.Equals(driveStr, StringComparison.InvariantCultureIgnoreCase) == false))
                {
                    break;
                }
                driveChar++;
            }
            return driveChar;
        }

        protected char DriveLatter { get; set; }

        public void Unmount()
        {
            try
            {
                if (IsMounted)
                {
                    DokanNet.DokanUnmount(DriveLatter);
                    IsMounted = false;
                }
            }
            catch (Exception)
            {
            }

        }

        private bool _isMounted;
        public IWaitScreenService WaitScreenService { get; private set; }
        public IMediaFSOperations MediaFSOperations { get; private set; }

        public bool IsMounted
        {
            get { return _isMounted; }
            set
            {
                _isMounted = value;
                OnPropertyChanged("IsMounted");
            }
        }

        public void ToggleMount(IWaitScreenService waitScreenService)
        {
            WaitScreenService = waitScreenService;
            if(IsMounted==false)
            {
                WaitScreenService.Show(DatabaseManager.Instance.GetLibraryItemsCount(),0);
                InitializeMemoryOperations();
            }
            if (IsMounted)
                Unmount();
            else
            {
                Mount();
            }
        }
    }
}
